<!-- eslint-disable no-param-reassign, function-paren-newline, max-len -->
<script>
import { h } from 'vue';

export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Splitpanes',

  provide() {
    return {
      requestUpdate: this.requestUpdate,
      onPaneAdd: this.onPaneAdd,
      onPaneRemove: this.onPaneRemove,
      onPaneClick: this.onPaneClick,
      onMouseMove: this.onMouseMove,
      resizeNextPaneSize: this.resizeNextPaneSize,
      resizeLastPaneSize: this.resizeLastPaneSize,
      checkHasNextPane: this.checkHasNextPane,
      checkHasLastPane: this.checkHasLastPane,
      getPaneSize: this.getPaneSize,
      horizontal: this.horizontal,
      getSplitSize: this.getSplitSize
    };
  },

  props: {
    /** 窗格拆分的方向 默认情况下为垂直，这意味着拆分器是垂直的，但您可以水平调整大小 */
    horizontal: { type: Boolean },
    /** 拖动分割线到达下一个分隔线时，是否推动下一个分割线。 */
    pushOtherPanes: { type: Boolean, default: false },
    /** 双击分割线以最大化下一个窗格 */
    dblClickSplitter: { type: Boolean, default: false },
    /** 支持从右向左的方向。 */
    rtl: { type: Boolean, default: false },
    /** 设置为true时显示第一个分割线。这允许在分割线双击时最大化第一个窗格。 */
    firstSplitter: { type: Boolean }
  },
  emits: ['ready', 'resize', 'resized', 'pane-click', 'pane-maximize', 'pane-add', 'pane-remove', 'splitter-click'],

  data: () => ({
    container: null,
    ready: false,
    panes: [],
    touch: {
      mouseDown: false,
      dragging: false,
      activeSplitter: null
    },
    splitterTaps: {
      // Used to detect double click on touch devices.
      splitter: null,
      timeoutId: null
    },
    splitWidth: null,
    splitHeight: null
  }),

  computed: {
    panesCount() {
      return this.panes.length;
    },
    // Indexed panes by `uid` of Pane components for fast lookup.
    // Every time a pane is destroyed this index is recomputed.
    indexedPanes() {
      return this.panes.reduce((obj, pane) => (obj[pane.id] = pane) && obj, {});
    }
  },

  watch: {
    panes: {
      // Every time a pane is updated, update the panes accordingly.
      deep: true,
      immediate: false,
      handler() {
        this.updatePaneComponents();
      }
    },
    horizontal() {
      this.updatePaneComponents();
    },
    firstSplitter() {
      this.redoSplitters();
    },
    dblClickSplitter(enable) {
      const splitters = [...this.container.querySelectorAll('.splitpanes__splitter')];
      splitters.forEach((splitter, i) => {
        splitter.ondblclick = enable ? (event) => this.onSplitterDblClick(event, i) : undefined;
      });
    }
  },

  beforeUnmount() {
    // Prevent emitting console warnings on hot reloading.
    this.ready = false;
  },

  mounted() {
    setTimeout(() => {
      this.container = this.$refs.container;
      this.checkSplitpanesNodes();
      this.redoSplitters();
      this.resetPaneSizes();
      this.$emit('ready');
      this.ready = true;
    }, 20);
    window.onresize = () => {
      // 针对 pane中存在设置了具体宽高的需要监听窗口变化重新计算宽高对应的百分比数值
      const filterData = this.panes.filter((item) => item.pxFlag > 0);
      if (filterData && filterData.length > 0) {
        this.panes.forEach((pane) => {
          // 循环清空初始化px转换为百分比设置的尺寸
          pane.newSize = null;
          if (!pane.pxSize) {
            // 清空页面配置中未配置尺寸的pane（这些pane会在页面初始化的时候组件为其计算百分比）
            pane.size = null;
          }
        });
        this.resetPaneSizes();
        this.$emit('onresize');
      }
    };
  },

  methods: {
    updatePaneComponents() {
      this.panes.forEach((pane) => {
        if (pane.newSize) {
          pane.update &&
            pane.update({
              [this.horizontal ? 'height' : 'width']: `${this.indexedPanes[pane.id].newSize}%`
            });
          this.indexedPanes[pane.id].newSize = null;
        } else {
          pane.update &&
            pane.update({
              [this.horizontal ? 'height' : 'width']: `${this.indexedPanes[pane.id].size}%`
            });
        }
      });
    },

    bindEvents() {
      document.addEventListener('mousemove', this.onMouseMove, { passive: false });
      document.addEventListener('mouseup', this.onMouseUp);

      // Passive: false to prevent scrolling while touch dragging.
      if ('ontouchstart' in window) {
        document.addEventListener('touchmove', this.onMouseMove, { passive: false });
        document.addEventListener('touchend', this.onMouseUp);
      }
    },

    unbindEvents() {
      document.removeEventListener('mousemove', this.onMouseMove, { passive: false });
      document.removeEventListener('mouseup', this.onMouseUp);

      if ('ontouchstart' in window) {
        document.removeEventListener('touchmove', this.onMouseMove, { passive: false });
        document.removeEventListener('touchend', this.onMouseUp);
      }
    },

    onMouseDown(event, splitterIndex) {
      this.bindEvents();
      this.touch.mouseDown = true;
      this.touch.activeSplitter = splitterIndex;
    },

    onMouseMove(event) {
      if (this.touch.mouseDown) {
        // Prevent scrolling while touch dragging (only works with an active event, eg. passive: false).
        event.preventDefault();
        this.touch.dragging = true;
        this.calculatePanesSize(this.getCurrentMouseDrag(event));
        this.$emit(
          'resize',
          this.panes.map((pane) => ({ min: pane.min, max: pane.max, size: pane.size }))
        );
      }
    },

    onMouseUp() {
      if (this.touch.dragging) {
        this.$emit(
          'resized',
          this.panes.map((pane) => ({ min: pane.min, max: pane.max, size: pane.size }))
        );
      }
      this.touch.mouseDown = false;
      // Keep dragging flag until click event is finished (click happens immediately after mouseup)
      // in order to prevent emitting `splitter-click` event if splitter was dragged.
      setTimeout(() => {
        this.touch.dragging = false;
        this.unbindEvents();
      }, 100);
    },

    // If touch device, detect double tap manually (2 taps separated by less than 500ms).
    onSplitterClick(event, splitterIndex) {
      if ('ontouchstart' in window) {
        event.preventDefault();

        // Detect splitter double taps if the option is on.
        if (this.dblClickSplitter) {
          if (this.splitterTaps.splitter === splitterIndex) {
            clearTimeout(this.splitterTaps.timeoutId);
            this.splitterTaps.timeoutId = null;
            this.onSplitterDblClick(event, splitterIndex);
            this.splitterTaps.splitter = null; // Reset for the next tap check.
          } else {
            this.splitterTaps.splitter = splitterIndex;
            this.splitterTaps.timeoutId = setTimeout(() => {
              this.splitterTaps.splitter = null;
            }, 500);
          }
        }
      }

      if (!this.touch.dragging) this.$emit('splitter-click', this.panes[splitterIndex]);
    },

    // On splitter dbl click or dbl tap maximize this pane.
    onSplitterDblClick(event, splitterIndex) {
      let totalMinSizes = 0;
      this.panes = this.panes.map((pane, i) => {
        pane.size = i === splitterIndex ? pane.max : pane.min;
        if (i !== splitterIndex) totalMinSizes += pane.min;

        return pane;
      });
      this.panes[splitterIndex].size -= totalMinSizes;
      this.$emit('pane-maximize', this.panes[splitterIndex]);
      this.$emit(
        'resized',
        this.panes.map((pane) => ({ min: pane.min, max: pane.max, size: pane.size }))
      );
    },

    onPaneClick(event, paneId) {
      this.$emit('pane-click', this.indexedPanes[paneId]);
    },

    // Get the cursor position relative to the splitpane container.
    getCurrentMouseDrag(event) {
      const rect = this.container.getBoundingClientRect();
      const { clientX, clientY } = 'ontouchstart' in window && event.touches ? event.touches[0] : event;

      return {
        x: clientX - rect.left,
        y: clientY - rect.top
      };
    },

    // Returns the drag percentage of the splitter relative to the 2 panes it's inbetween.
    // if the sum of size of the 2 cells is 60%, the dragPercentage range will be 0 to 100% of this 60%.
    getCurrentDragPercentage(drag) {
      drag = drag[this.horizontal ? 'y' : 'x'];
      // In the code bellow 'size' refers to 'width' for vertical and 'height' for horizontal layout.
      const containerSize = this.container[this.horizontal ? 'clientHeight' : 'clientWidth'];
      if (this.rtl && !this.horizontal) drag = containerSize - drag;

      return (drag * 100) / containerSize;
    },

    calculatePanesSize(drag) {
      const splitterIndex = this.touch.activeSplitter;
      let sums = {
        prevPanesSize: this.sumPrevPanesSize(splitterIndex),
        nextPanesSize: this.sumNextPanesSize(splitterIndex),
        prevReachedMinPanes: 0,
        nextReachedMinPanes: 0
      };

      const minDrag = 0 + (this.pushOtherPanes ? 0 : sums.prevPanesSize);
      const maxDrag = 100 - (this.pushOtherPanes ? 0 : sums.nextPanesSize);
      const dragPercentage = Math.max(Math.min(this.getCurrentDragPercentage(drag), maxDrag), minDrag);

      // If not pushing other panes, panes to resize are right before and right after splitter.
      let panesToResize = [splitterIndex, splitterIndex + 1];
      let paneBefore = this.panes[panesToResize[0]] || null;
      let paneAfter = this.panes[panesToResize[1]] || null;

      const paneBeforeMaxReached = paneBefore.max < 100 && dragPercentage >= paneBefore.max + sums.prevPanesSize;
      const paneAfterMaxReached = paneAfter.max < 100 && dragPercentage <= 100 - (paneAfter.max + this.sumNextPanesSize(splitterIndex + 1));
      // Prevent dragging beyond pane max.
      if (paneBeforeMaxReached || paneAfterMaxReached) {
        if (paneBeforeMaxReached) {
          paneBefore.size = paneBefore.max;
          paneAfter.size = Math.max(100 - paneBefore.max - sums.prevPanesSize - sums.nextPanesSize, 0);
        } else {
          paneBefore.size = Math.max(100 - paneAfter.max - sums.prevPanesSize - this.sumNextPanesSize(splitterIndex + 1), 0);
          paneAfter.size = paneAfter.max;
        }
        return;
      }

      // When pushOtherPanes = true, find the closest expanded pane on each side of the splitter.
      if (this.pushOtherPanes) {
        const vars = this.doPushOtherPanes(sums, dragPercentage);
        if (!vars) return; // Prevent other calculation.
        ({ sums, panesToResize } = vars);
        paneBefore = this.panes[panesToResize[0]] || null;
        paneAfter = this.panes[panesToResize[1]] || null;
      }

      if (paneBefore !== null) {
        paneBefore.size = Math.min(
          Math.max(dragPercentage - sums.prevPanesSize - sums.prevReachedMinPanes, paneBefore.min),
          paneBefore.max
        );
      }
      if (paneAfter !== null) {
        paneAfter.size = Math.min(
          Math.max(100 - dragPercentage - sums.nextPanesSize - sums.nextReachedMinPanes, paneAfter.min),
          paneAfter.max
        );
      }
    },

    doPushOtherPanes(sums, dragPercentage) {
      const splitterIndex = this.touch.activeSplitter;
      const panesToResize = [splitterIndex, splitterIndex + 1];
      // Pushing Down.
      // Going smaller than the current pane min size: take the previous expanded pane.
      if (dragPercentage < sums.prevPanesSize + this.panes[panesToResize[0]].min) {
        panesToResize[0] = this.findPrevExpandedPane(splitterIndex).index;

        sums.prevReachedMinPanes = 0;
        // If pushing a n-2 or less pane, from splitter, then make sure all in between is at min size.
        if (panesToResize[0] < splitterIndex) {
          this.panes.forEach((pane, i) => {
            if (i > panesToResize[0] && i <= splitterIndex) {
              pane.size = pane.min;
              sums.prevReachedMinPanes += pane.min;
            }
          });
        }
        sums.prevPanesSize = this.sumPrevPanesSize(panesToResize[0]);
        // If nothing else to push down, cancel dragging.
        if (panesToResize[0] === undefined) {
          sums.prevReachedMinPanes = 0;
          this.panes[0].size = this.panes[0].min;
          this.panes.forEach((pane, i) => {
            if (i > 0 && i <= splitterIndex) {
              pane.size = pane.min;
              sums.prevReachedMinPanes += pane.min;
            }
          });
          this.panes[panesToResize[1]].size = 100 - sums.prevReachedMinPanes - this.panes[0].min - sums.prevPanesSize - sums.nextPanesSize;
          return null;
        }
      }
      // Pushing Up.
      // Pushing up beyond min size is reached: take the next expanded pane.
      if (dragPercentage > 100 - sums.nextPanesSize - this.panes[panesToResize[1]].min) {
        panesToResize[1] = this.findNextExpandedPane(splitterIndex).index;
        sums.nextReachedMinPanes = 0;
        // If pushing a n+2 or more pane, from splitter, then make sure all in between is at min size.
        if (panesToResize[1] > splitterIndex + 1) {
          this.panes.forEach((pane, i) => {
            if (i > splitterIndex && i < panesToResize[1]) {
              pane.size = pane.min;
              sums.nextReachedMinPanes += pane.min;
            }
          });
        }
        sums.nextPanesSize = this.sumNextPanesSize(panesToResize[1] - 1);
        // If nothing else to push up, cancel dragging.
        if (panesToResize[1] === undefined) {
          sums.nextReachedMinPanes = 0;
          this.panes[this.panesCount - 1].size = this.panes[this.panesCount - 1].min;
          this.panes.forEach((pane, i) => {
            if (i < this.panesCount - 1 && i >= splitterIndex + 1) {
              pane.size = pane.min;
              sums.nextReachedMinPanes += pane.min;
            }
          });
          this.panes[panesToResize[0]].size =
            100 - sums.prevPanesSize - sums.nextReachedMinPanes - this.panes[this.panesCount - 1].min - sums.nextPanesSize;
          return null;
        }
      }
      return { sums, panesToResize };
    },

    sumPrevPanesSize(splitterIndex) {
      return this.panes.reduce((total, pane, i) => total + (i < splitterIndex ? pane.size : 0), 0);
    },

    sumNextPanesSize(splitterIndex) {
      return this.panes.reduce((total, pane, i) => total + (i > splitterIndex + 1 ? pane.size : 0), 0);
    },

    // Return the previous pane from siblings which has a size (width for vert or height for horz) of more than 0.
    findPrevExpandedPane(splitterIndex) {
      const pane = [...this.panes].reverse().find((item) => item.index < splitterIndex && item.size > item.min);
      return pane || {};
    },

    // Return the next pane from siblings which has a size (width for vert or height for horz) of more than 0.
    findNextExpandedPane(splitterIndex) {
      const pane = this.panes.find((item) => item.index > splitterIndex + 1 && item.size > item.min);
      return pane || {};
    },

    checkSplitpanesNodes() {
      const children = Array.from(this.container.children);
      children.forEach((child) => {
        const isPane = child.classList.contains('splitpanes__pane');
        const isSplitter = child.classList.contains('splitpanes__splitter');

        // Node is not a Pane or a splitter: remove it.
        if (!isPane && !isSplitter) {
          child.parentNode.removeChild(child); // el.remove() doesn't work on IE11.
          console.warn('Splitpanes: Only <pane> elements are allowed at the root of <splitpanes>. One of your DOM nodes was removed.');
        }
      });
    },

    addSplitter(paneIndex, nextPaneNode, isVeryFirst = false) {
      const splitterIndex = paneIndex - 1;
      const pane = this.panes[splitterIndex];
      const elm = document.createElement('div');
      elm.classList.add('splitpanes__splitter');
      let draggable = true;
      if (pane) {
        draggable = pane.draggable;
      }
      if (draggable) {
        if (!isVeryFirst) {
          elm.onmousedown = (event) => this.onMouseDown(event, splitterIndex);

          if (typeof window !== 'undefined' && 'ontouchstart' in window) {
            elm.ontouchstart = (event) => this.onMouseDown(event, splitterIndex);
          }
          elm.onclick = (event) => this.onSplitterClick(event, splitterIndex + 1);
        }

        if (this.dblClickSplitter) {
          elm.ondblclick = (event) => this.onSplitterDblClick(event, splitterIndex + 1);
        }
      } else {
        elm.classList.add('nodrag');
      }
      nextPaneNode.parentNode.insertBefore(elm, nextPaneNode);
    },

    removeSplitter(node) {
      node.onmousedown = undefined;
      node.onclick = undefined;
      node.ondblclick = undefined;
      node.parentNode.removeChild(node); // el.remove() doesn't work on IE11.
    },

    redoSplitters() {
      const children = Array.from(this.container.children);
      children.forEach((el) => {
        if (el.className.includes('splitpanes__splitter')) this.removeSplitter(el);
      });
      let paneIndex = 0;
      children.forEach((el) => {
        if (el.className.includes('splitpanes__pane')) {
          if (!paneIndex && this.firstSplitter) this.addSplitter(paneIndex, el, true);
          else if (paneIndex) this.addSplitter(paneIndex, el);
          paneIndex += 1;
        }
      });
    },

    // Called by Pane component on programmatic resize.
    requestUpdate({ target, ...args }) {
      const pane = this.indexedPanes[target._.uid];
      Object.entries(args).forEach(([key, value]) => (pane[key] = value));
    },
    /** 折叠收起时同时需要修改下一个pane的尺寸 */
    resizeNextPaneSize({ target, changeSize }) {
      const pane = this.indexedPanes[target._.uid];
      if (pane.index < this.panes.length) {
        this.panes[pane.index + 1].size = this.panes[pane.index + 1].size + parseFloat(changeSize);
      }
    },
    /** 折叠收起时同时需要修改上一个pane的尺寸 */
    resizeLastPaneSize({ target, changeSize }) {
      const pane = this.indexedPanes[target._.uid];
      if (pane.index > 0) {
        if (isNaN(this.panes[pane.index - 1].size)) {
          const lastPaneSize = document.getElementById(`splitpanes__pane${this.panes[pane.index - 1].id}`).style.width?.replace('%', '');
          this.panes[pane.index - 1].size = parseFloat(lastPaneSize);
        }
        this.panes[pane.index - 1].size = this.panes[pane.index - 1].size + parseFloat(changeSize);
      }
    },
    /** 判断是否存在下一个pane */
    checkHasNextPane({ target }) {
      const pane = this.indexedPanes[target._.uid];
      if (pane.index === this.panes.length - 1) {
        return false;
      }
      return true;
    },
    /** 判断是否存在上一个pane */
    checkHasLastPane({ target }) {
      const pane = this.indexedPanes[target._.uid];
      if (pane.index === 0) {
        return false;
      }
      return true;
    },
    /** 获取当前pane Size */
    getPaneSize({ target }) {
      const pane = this.indexedPanes[target._.uid];
      if (isNaN(pane.size)) {
        const paneSize = document.getElementById(`splitpanes__pane${target._.uid}`).style.width?.replace('%', '');
        if (paneSize) {
          pane.size = parseFloat(paneSize);
        }
      }
      return pane.size;
    },
    getSplitSize() {
      this.splitWidth = this.$refs.container?.clientWidth;
      this.splitHeight = this.$refs.container?.clientHeight;
      return { splitHeight: this.splitHeight, splitWidth: this.splitWidth };
    },
    /** 重新根据具体px值计算百分比 */
    setSize(pane) {
      if (pane.pxSize !== null) {
        if (pane.pxFlag > 0) {
          // 具体宽高
          const splitSize = this.getSplitSize();
          const { splitWidth } = splitSize;
          const { splitHeight } = splitSize;
          let realSize = null;
          if (this.horizontal) {
            // 水平布局高度计算百分比
            realSize = (parseFloat(pane.pxSize) / parseFloat(splitHeight)) * 100;
          } else {
            // 垂直布局宽度计算百分比
            realSize = (parseFloat(pane.pxSize) / parseFloat(splitWidth)) * 100;
          }
          realSize = realSize.toFixed(2);
          pane.newSize = realSize;
          pane.size = realSize;
        }
      }
    },

    onPaneAdd(pane) {
      // 1. Add pane to array at the same index it was inserted in the <splitpanes> tag.
      let index = -1;
      Array.from(pane.$el.parentNode.children).some((el) => {
        if (el.className.includes('splitpanes__pane')) index += 1;

        return el === pane.$el;
      });

      const min = parseFloat(pane.minSize);
      const max = parseFloat(pane.maxSize);
      this.panes.splice(index, 0, {
        id: pane._.uid,
        index,
        min: isNaN(min) ? 0 : min,
        max: isNaN(max) ? 100 : max,
        size: pane.size === null ? null : parseFloat(pane.size),
        pxSize: pane.size,
        pxFlag: pane.size === null ? null : pane.size.indexOf('px'),
        givenSize: pane.size,
        draggable: pane.draggable,
        update: pane.update
      });
      // Redo indexes after insertion for other shifted panes.
      this.panes.forEach((pane, i) => (pane.index = i));

      if (this.ready) {
        this.$nextTick(() => {
          // 2. Add the splitter.
          this.redoSplitters();

          // 3. Resize the panes.
          this.resetPaneSizes({ addedPane: this.panes[index] });

          // 4. Fire `pane-add` event.
          this.$emit('pane-add', {
            index,
            panes: this.panes.map((pane) => ({ min: pane.min, max: pane.max, size: pane.size }))
          });
        });
      }
    },

    onPaneRemove(pane) {
      // 1. Remove the pane from array and redo indexes.
      const index = this.panes.findIndex((item) => item.id === pane._.uid);
      const removed = this.panes.splice(index, 1)[0];
      this.panes.forEach((item, i) => (item.index = i));

      this.$nextTick(() => {
        // 2. Remove the splitter.
        this.redoSplitters();

        // 3. Resize the panes.
        this.resetPaneSizes({ removedPane: { ...removed, index } });

        // 4. Fire `pane-remove` event.
        this.$emit('pane-remove', {
          removed,
          panes: this.panes.map((pane) => ({ min: pane.min, max: pane.max, size: pane.size }))
        });
      });
    },

    resetPaneSizes(changedPanes = {}) {
      if (!changedPanes.addedPane && !changedPanes.removedPane) {
        this.initialPanesSizing();
      } else if (this.panes.some((pane) => pane.givenSize !== null || pane.min || pane.max < 100)) {
        this.equalizeAfterAddOrRemove(changedPanes);
      } else {
        this.equalize();
      }

      if (this.ready) {
        this.$emit(
          'resized',
          this.panes.map((pane) => ({ min: pane.min, max: pane.max, size: pane.size }))
        );
      }
    },

    equalize() {
      const equalSpace = 100 / this.panesCount;
      let leftToAllocate = 0;
      const ungrowable = [];
      const unshrinkable = [];

      this.panes.forEach((pane) => {
        pane.size = Math.max(Math.min(equalSpace, pane.max), pane.min);

        leftToAllocate -= pane.size;
        if (pane.size >= pane.max) ungrowable.push(pane.id);
        if (pane.size <= pane.min) unshrinkable.push(pane.id);
      });

      if (leftToAllocate > 0.1) this.readjustSizes(leftToAllocate, ungrowable, unshrinkable);
    },

    initialPanesSizing() {
      let leftToAllocate = 100;
      const ungrowable = [];
      const unshrinkable = [];
      let definedSizes = 0;
      // Check if pre-allocated space is 100%.
      this.panes.forEach((pane) => {
        this.setSize(pane);
        leftToAllocate -= pane.size;
        if (pane.size !== null) definedSizes += 1;
        if (pane.size >= pane.max) ungrowable.push(pane.id);
        if (pane.size <= pane.min) unshrinkable.push(pane.id);
      });
      // set pane sizes if not set.
      let leftToAllocate2 = 100;
      if (leftToAllocate > 0.1) {
        this.panes.forEach((pane) => {
          if (pane.size === null) {
            pane.size = Math.max(Math.min(leftToAllocate / (this.panesCount - definedSizes), pane.max), pane.min);
          }
          leftToAllocate2 -= pane.size;
        });
        if (leftToAllocate2 > 0.1) this.readjustSizes(leftToAllocate, ungrowable, unshrinkable);
      }
    },
    equalizeAfterAddOrRemove({ addedPane } = {}) {
      let equalSpace = 100 / this.panesCount;
      let leftToAllocate = 0;
      const ungrowable = [];
      const unshrinkable = [];

      if (addedPane && addedPane.givenSize !== null) {
        equalSpace = (100 - addedPane.givenSize) / (this.panesCount - 1);
      }

      // Check if pre-allocated space is 100%.
      this.panes.forEach((pane) => {
        leftToAllocate -= pane.size;
        if (pane.size >= pane.max) ungrowable.push(pane.id);
        if (pane.size <= pane.min) unshrinkable.push(pane.id);
      });

      if (Math.abs(leftToAllocate) < 0.1) return; // Ok.

      this.panes.forEach((pane) => {
        if (!addedPane || !addedPane.givenSize || addedPane.id !== pane.id) {
          pane.size = Math.max(Math.min(equalSpace, pane.max), pane.min);
        }

        leftToAllocate -= pane.size;
        if (pane.size >= pane.max) ungrowable.push(pane.id);
        if (pane.size <= pane.min) unshrinkable.push(pane.id);
      });

      if (leftToAllocate > 0.1) this.readjustSizes(leftToAllocate, ungrowable, unshrinkable);
    },
    /* eslint-disable max-len */
    /* recalculatePaneSizes ({ addedPane, removedPane } = {}) {
      let leftToAllocate = 100
      let equalSpaceToAllocate = leftToAllocate / this.panesCount
      let ungrowable = []
      let unshrinkable = []

      // When adding a pane with no size, apply min-size if defined otherwise divide another pane
      // (next or prev) in 2.
      // if (addedPane && addedPane.size === null) {
      //   if (addedPane.min) addedPane.size = addedPane.min
      //   else {
      //     const paneToDivide = this.panes[addedPane.index + 1] || this.panes[addedPane.index - 1]
      //     if (paneToDivide) {
      //       // @todo: Dividing that pane in 2 could be incorrect if becoming lower than its min size.
      //       addedPane.size = paneToDivide.size / 2
      //       paneToDivide.size /= 2
      //     }
      //   }
      // }

      this.panes.forEach((pane, i) => {
        // Added pane - reduce the size of the next pane.
        if (addedPane && addedPane.index + 1 === i) {
          pane.size = Math.max(Math.min(100 - this.sumPrevPanesSize(i) - this.sumNextPanesSize(i + 1), pane.max), pane.min)
          // @todo: if could not allocate correctly, try to allocate in the next pane straight away,
          // then still do the second loop if not correct.
        }

        // Removed pane - increase the size of the next pane.
        else if (removedPane && removedPane.index === i) {
          pane.size = Math.max(Math.min(100 - this.sumPrevPanesSize(i) - this.sumNextPanesSize(i + 1), pane.max), pane.min)
          // @todo: if could not allocate correctly, try to allocate in the next pane straight away,
          // then still do the second loop if not correct.
        }

        // Initial load and on demand recalculation.
        else if (!addedPane && !removedPane && pane.size === null) {
          pane.size = Math.max(Math.min(equalSpaceToAllocate, pane.max), pane.min)
        }

        leftToAllocate -= pane.size

        if (pane.size >= pane.max) ungrowable.push(pane.id)
        if (pane.size <= pane.min) unshrinkable.push(pane.id)
      })

      // Do one more loop to adjust sizes if still wrong.
      // > 0.1: Prevent maths rounding issues due to bytes.
      if (Math.abs(leftToAllocate) > 0.1) this.readjustSizes(leftToAllocate, ungrowable, unshrinkable)
    }, */

    // Second loop to adjust sizes now that we know more about the panes constraints.
    readjustSizes(leftToAllocate, ungrowable, unshrinkable) {
      let equalSpaceToAllocate = 0;
      if (leftToAllocate > 0) {
        equalSpaceToAllocate = leftToAllocate / (this.panesCount - ungrowable.length);
      } else equalSpaceToAllocate = leftToAllocate / (this.panesCount - unshrinkable.length);

      this.panes.forEach((pane) => {
        if (leftToAllocate > 0 && !ungrowable.includes(pane.id)) {
          // Need to diff the size before and after to get the exact allocated space.
          const newPaneSize = Math.max(Math.min(pane.size + equalSpaceToAllocate, pane.max), pane.min);
          const allocated = newPaneSize - pane.size;
          leftToAllocate -= allocated;
          pane.size = newPaneSize;
        } else if (!unshrinkable.includes(pane.id)) {
          // Need to diff the size before and after to get the exact allocated space.
          const newPaneSize = Math.max(Math.min(pane.size + equalSpaceToAllocate, pane.max), pane.min);
          const allocated = newPaneSize - pane.size;
          leftToAllocate -= allocated;
          pane.size = newPaneSize;
        }
        // Update each pane through the registered `update` method.
        pane.update({
          [this.horizontal ? 'height' : 'width']: `${this.indexedPanes[pane.id].size}%`
        });
      });

      if (Math.abs(leftToAllocate) > 0.1) {
        // > 0.1: Prevent maths rounding issues due to bytes.
        // Don't emit on hot reload when Vue destroys panes.
        this.$nextTick(() => {
          if (this.ready) {
            // eslint-disable-next-line no-console
            console.warn('Splitpanes: Could not resize panes correctly due to their constraints.');
          }
        });
      }
    }

    /* distributeEmptySpace () {
      let growablePanes = []
      let collapsedPanesCount = 0
      let growableAmount = 0 // Total of how much the current panes can grow to fill blank space.
      let spaceToDistribute = 100 - this.panes.reduce((sum, pane) => (sum += pane.size) && sum, 0)
      // Do a first loop to determine if we can distribute the new blank space between all the
      // expandedPanes, without expanding the collapsed ones.
      this.panes.forEach(pane => {
        if (pane.size < pane.max) growablePanes.push(pane)

        if (!pane.size) collapsedPanesCount++
        else growableAmount += pane.max - pane.size
      })

      // If the blank space to distribute is too great for the expanded panes, also expand collapsed ones.
      let expandCollapsedPanes = growableAmount < spaceToDistribute

      // New space to distribute equally.
      let growablePanesCount = (growablePanes.length - (expandCollapsedPanes ? 0 : collapsedPanesCount))
      let equalSpaceToDistribute = spaceToDistribute / growablePanesCount
      // if (growablePanesCount === 1) equalSpace = 100 / this.panesCount
      let spaceLeftToDistribute = spaceToDistribute

      // Now add the equalSpaceToDistribute to each pane size accordingly.
      growablePanes.forEach(pane => {
        if (pane.size < pane.max && (pane.size || (!pane.size && expandCollapsedPanes))) {
          const newSize = Math.min(pane.size + equalSpaceToDistribute, pane.max)
          let allocatedSpace = (newSize - pane.size)
          spaceLeftToDistribute -= allocatedSpace
          pane.size = newSize
          // If the equalSpaceToDistribute is not fully added to the current pane, distribute the remainder
          // to the next panes.
          // Also fix decimal issue due to bites - E.g. calculating 8.33 and getting 8.3299999999999=
          if (equalSpaceToDistribute - allocatedSpace > 0.1) {
            equalSpaceToDistribute = spaceLeftToDistribute / (--growablePanesCount)
          }
        }
      })

      /* Disabled otherwise will show up on hot reload.
      // if there is still space to allocate show warning message.
      if (this.panesCount && ~~spaceLeftToDistribute) {
        // eslint-disable-next-line no-console
        console.warn('Splitpanes: Could not distribute all the empty space between panes due to their constraints.')
      } *\/

      this.$emit('resized', this.panes.map(pane => ({ min: pane.min, max: pane.max, size: pane.size })))
    } */
  },

  render() {
    return h(
      'div',
      {
        ref: 'container',
        class: [
          'splitpanes',
          `splitpanes--${this.horizontal ? 'horizontal' : 'vertical'}`,
          {
            'splitpanes--dragging': this.touch.dragging
          }
        ]
      },
      this.$slots.default()
    );
  }
};
</script>
<!-- eslint-disable max-lines -->
<style lang="less">
.splitpanes__pane.close {
  .splitpanes__pane {
    > .goLeft,
    > .goTop {
      display: none;
    }
  }

  > .goLeft {
    right: -18px;
    z-index: 2;
    display: block;
    transform: rotate(180deg);
  }

  > .goTop {
    bottom: -18px;
    z-index: 2;
    display: block;
    transform: rotate(180deg);
  }

  > .goRight {
    left: -18px;
    z-index: 2;
    display: block;
    transform: rotate(180deg);
  }

  > .goBottom {
    top: -18px;
    z-index: 2;
    display: block;
    transform: rotate(180deg);
  }
}

.splitpanes:has(.close) {
  &--vertical > .splitpanes__splitter {
    min-width: 0;
    background: @layout-body-background;
    cursor: col-resize;
  }

  &--horizontal > .splitpanes__splitter {
    min-height: 0;
    background: @layout-body-background;
    cursor: row-resize;
  }
}

.splitpanes__pane {
  position: relative;
  background: #fff;

  .splitpanes__inner {
    position: absolute;
    width: 100%;
    height: 100%;
    overflow: hidden;
  }

  > .goLeft,
  > .goTop,
  > .goBottom,
  > .goRight {
    display: none;
  }

  &:hover {
    > .goLeft,
    > .goTop,
    > .goBottom,
    > .goRight {
      display: block;
    }
  }

  .goLeft {
    position: absolute;
    top: 50%;
    right: 0;
    width: 0;
    height: 48px;
    margin-top: -24px;
    border: 0;
    border-top: 8px solid transparent;
    border-right: 12px solid @layout-body-background;
    border-bottom: 8px solid transparent;
    cursor: pointer;

    .avic-icon {
      margin-left: -5px;
      color: @primary-color;
      font-size: 24px;
      vertical-align: -0.25em;
    }
  }

  .goTop {
    position: absolute;
    bottom: 0;
    left: 50%;
    width: 48px;
    height: 0;
    margin-left: -24px;
    border: 0;
    border-right: 8px solid transparent;
    border-bottom: 12px solid @layout-body-background;
    border-left: 8px solid transparent;
    cursor: pointer;

    .avic-icon {
      position: absolute;
      margin-top: -12px;
      margin-left: 2px;
      color: @primary-color;
      font-size: 24px;
      transform: rotate(90deg);
    }
  }

  .goRight {
    position: absolute;
    top: 50%;
    left: 0;
    width: 0;
    height: 48px;
    margin-top: -24px;
    border: 0;
    border-top: 8px solid transparent;
    border-bottom: 8px solid transparent;
    border-left: 12px solid @layout-body-background;
    cursor: pointer;

    .avic-icon {
      position: absolute;
      top: 1px;
      margin-left: -19px;
      color: @primary-color;
      font-size: 24px;
      vertical-align: -0.25em;
      transform: rotate(180deg);
    }
  }

  .goBottom {
    position: absolute;
    top: 0;
    left: 50%;
    width: 48px;
    height: 0;
    margin-left: -24px;
    border: 0;
    border-top: 12px solid @layout-body-background;
    border-right: 8px solid transparent;
    border-left: 8px solid transparent;
    cursor: pointer;

    .avic-icon {
      position: absolute;
      margin-top: -26px;
      margin-left: 8px;
      color: @primary-color;
      font-size: 24px;
      transform: rotate(270deg);
    }
  }
}

.splitpanes {
  display: flex;
  width: 100%;
  height: 100%;
  flex: 1 0 0;
  .splitpanes__pane {
  }
  &.loading::before {
    position: absolute;
    top: 8px;
    right: 8px;
    bottom: 8px;
    left: 8px;
    z-index: 33;
    background: #fff;
    content: '';
  }

  &--vertical {
    flex-direction: row;
  }

  &--horizontal {
    flex-direction: column;
  }

  &--dragging * {
    user-select: none;
  }

  &__pane {
    width: 100%;
    height: 100%;
    // overflow: hidden;
    background-color: #fff;

    .splitpanes--vertical & {
      transition: width 0s ease-out;
    }

    .splitpanes--horizontal & {
      transition: height 0s ease-out;
    }

    .splitpanes--dragging & {
      transition: none;
    }
  }

  // Disable default zoom behavior on touch device when double tapping splitter.
  &__splitter {
    touch-action: none;
  }

  &--vertical > .splitpanes__splitter {
    min-width: 6px;
    background: @layout-body-background;
    cursor: col-resize;

    &.nodrag {
      cursor: initial;

      &:before,
      &:after {
        display: none;
      }
    }
  }

  &--horizontal > .splitpanes__splitter {
    min-height: 6px;
    background: @layout-body-background;
    cursor: row-resize;

    &.nodrag {
      cursor: initial;

      &:before,
      &:after {
        display: none;
      }
    }
  }
}

.splitpanes {
  .splitpanes__splitter {
    position: relative;
    flex-shrink: 0;
    box-sizing: border-box;

    &::before,
    &::after {
      position: absolute;
      top: 50%;
      left: 50%;
      background-color: @text-color-less;
      transition: background-color 0.3s;
      content: '';
    }

    &:hover:before,
    &:hover:after {
      background-color: @primary-color;
    }

    &:first-child {
      cursor: auto;
    }
  }
}

&.splitpanes .splitpanes .splitpanes__splitter {
  z-index: 1;
}

&.splitpanes--vertical > .splitpanes__splitter,
.splitpanes--vertical > .splitpanes__splitter {
  width: 6px;
  overflow: hidden;

  &:before,
  &:after {
    width: 1px;
    height: 30px;
    transform: translateY(-50%);
  }

  &:before {
    margin-left: -2px;
  }

  &:after {
    margin-left: 1px;
  }
}

&.splitpanes--horizontal > .splitpanes__splitter,
.splitpanes--horizontal > .splitpanes__splitter {
  height: 6px;
  overflow: hidden;

  &:before,
  &:after {
    width: 30px;
    height: 1px;
    transform: translateX(-50%);
  }

  &:before {
    margin-top: -2px;
  }

  &:after {
    margin-top: 1px;
  }
}
</style>